package com.young.common.client.ums;

import com.young.common.config.ICommonInterceptor;
import com.young.common.util.*;
import com.young.interfaces.ums.model.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLEncoder;

/**
 * ums客户端拦截器
 * @author imrookie
 * @date 2018/10/1
 */
@Component("umsClientInterceptor")
@ConditionalOnProperty(
        name = "young.interceptor.ums-client-interceptor.loginUserService",//只要指定了参数loginUserService就加载,为空也加载
        matchIfMissing = false
)
@ConfigurationProperties(prefix="young.interceptor.ums-client-interceptor")
public class UmsClientInterceptor implements ICommonInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(UmsClientInterceptor.class);

    @Resource
    private ILoginUser loginUser;

    /**
     * 是否开启单点验证
     */
    private boolean ssoCheck;
    /**
     * 单点验证白名单
     */
    private String[] ssoWhite;
    /**
     * 验证失败后重定向地址
     */
    private String ssoRedirectUrl;
    /**
     * 是否开启权限校验
     */
    private boolean authCheck;
    /**
     * 是否启用日志MDC
     */
    private boolean enableMdc;
    /**
     * mdc的关键字
     */
    private String mdcKeyword;

    /**
     * 判断url是否需要登录
     * 白名单url或者重定向url不需要登录即可访问
     * @param curUri 请求地址,servletPath
     * @return
     */
    private boolean isNeedLoginUrl(String curUri){
        PathMatcher matcher = new AntPathMatcher();
        if (ssoWhite != null && ssoWhite.length > 0) {
            for(String url : ssoWhite){
                if(matcher.match(url, curUri)){//匹配
                    logger.debug("[UmsClientInterceptor] ServletPath[{}]属于白名单[{}]", curUri, url);
                    return false;
                }
            }
        }
        if (curUri.equals(ssoRedirectUrl)){
            logger.debug("[UmsClientInterceptor] ServletPath[{}]属于白名单[{}]", curUri, ssoRedirectUrl);
            return false;
        }
        return true;
    }

    //controller执行前
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        logger.debug("[UmsClientInterceptor] controller前置处理,URL={}", request.getRequestURL());
        //设置用户上下文
        User user = loginUser.getLoginUser(request);
        //当单点验证开关开启且用户信息无效并且当前请求需要登录时,对请求做拦截处理
        if (ssoCheck && user == null && this.isNeedLoginUrl(request.getServletPath())){
            logger.info("[UmsClientInterceptor] 当前请求登录用户信息无效,需重新登录,IP={},URL={}", ClientInfoUtil.getClientIpAddr(request), request.getRequestURL());
            if (CommonUtil.isAjaxRequest(request)){//ajax请求
                //写入失败结果
                this.write2Response(request, response, Result.build(Result.CodeEnum.SESSION_TIMEOUT));
            } else {
                if (StringUtils.isNotBlank(ssoRedirectUrl)) {
                    //请求的url
                    String url = request.getRequestURL().toString();
                    response.sendRedirect(ssoRedirectUrl + (ssoRedirectUrl.indexOf("?") > 0 ? "&" : "?") + "fromUrl=" + URLEncoder.encode(url, "UTF-8"));
                } else {
                    logger.warn("[umsClientInterceptor] ssoRedirectUrl参数无效,请检查young.interceptor.ums-client-interceptor.ssoRedirectUrl配置是否正常");
                }
            }
            return false;
        }


        //权限校验, 开关打开且登陆用户有效时验证
        if (this.authCheck && user != null) {
            String uri = request.getRequestURI();
            if (loginUser.canAccessUri(user, uri)) {
                logger.debug("[umsClientInterceptor] 当前请求uri={},用户id={},可以访问该uri", uri, user.getId());//debug
            } else {
                logger.info("[umsClientInterceptor] 当前请求uri={},用户id={},无权限访问", uri, user.getId());
                //写入失败结果
                this.write2Response(request, response, Result.build(Result.CodeEnum.NOT_AUTH));
                return false;
            }

        }

        //mdc处理
        if (this.enableMdc && user != null){
            MDC.put(this.mdcKeyword, user.getId());
        }
        //设置用户到上下文
        UmsClientContext.setLoginUser(user);
        logger.debug("[umsClientInterceptor] 设置用户上下文:{}", user);

        return true;
    }

    //controller执行后,视图渲染前
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        logger.debug("[UmsClientInterceptor] controller后置处理,URL={}", request.getRequestURL());
    }

    //做最后的处理,视图已经渲染完毕
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        logger.debug("[UmsClientInterceptor] 最终处理,URL={}", request.getRequestURL());
        //卸载当前用户上下文
        UmsClientContext.removeLoginUser();
        //mdc处理
        if (this.enableMdc){
            MDC.remove(this.mdcKeyword);
        }
    }

    /**
     * 注册拦截器
     * 接管WebMvcConfig中的addInterceptors
     *
     * @param registry
     */
    @Override
    public InterceptorRegistration addInterceptors(InterceptorRegistry registry) {
        if (this.loginUser == null){
            logger.error("[UmsClientInterceptor] ILoginUser服务无效,请检查young.interceptor.ums-client-interceptor.loginUserService配置是否正常");
            throw new IllegalArgumentException();
        }
        //当启用mdc却没有配置关键字时,自动关闭mdc功能
        if (this.enableMdc && StringUtils.isBlank(this.mdcKeyword)){
            logger.warn("[UmsClientInterceptor] 配置参数告警: 启用MDC功能(enableMdc=true)却未找到正确的MDC关键字配置(mdcKeyword),已自动关闭MDC功能,请知晓!");
            this.enableMdc = false;
        }
        //将自身注册到拦截器注册器中
        InterceptorRegistration interceptorRegistration = registry.addInterceptor(this).addPathPatterns("/**");//.excludePathPatterns("/static/**", "/page/**", "/favicon.ico");
        logger.info("[UmsClientInterceptor] UmsClientInterceptor拦截器注册完成,ssoCheck={},enableMdc={},mdcKeyword={},ssoRedirectUrl={},ssoWhite={},authCheck={}"
                , this.ssoCheck, this.enableMdc, this.mdcKeyword, this.ssoRedirectUrl, this.ssoWhite, this.authCheck);
        return interceptorRegistration;
    }

    /**
     * 写入结果到响应流
     * @param request 请求
     * @param response 响应
     * @param result 返回结果
     */
    private void write2Response(HttpServletRequest request, HttpServletResponse response, Result result) {
        response.setCharacterEncoding(CommonConstants.CHARSET);//编码
        response.setContentType("application/json; charset=utf-8");
        PrintWriter pw = null;
        try {
            pw = response.getWriter();
            //Result result = Result.build(Result.CodeEnum.SESSION_TIMEOUT);
            pw.write(result.toString());
        } catch (IOException e) {
            logger.error("[UmsClientInterceptor] 响应流写入异常", e);
        } catch (Exception e) {
            logger.error("[UmsClientInterceptor] 异常处理过程中发生异常!!!", e);
        } finally {
            try{
                if(pw!=null){
                    pw.close();
                }
            }catch (Exception e) {
                logger.error("[UmsClientInterceptor] 关闭PrintWriter异常", e);
            }
        }

    }

    public boolean isAuthCheck() {
        return authCheck;
    }

    public void setAuthCheck(boolean authCheck) {
        this.authCheck = authCheck;
    }

    public boolean isSsoCheck() {
        return ssoCheck;
    }

    public void setSsoCheck(boolean ssoCheck) {
        this.ssoCheck = ssoCheck;
    }

    public String getSsoRedirectUrl() {
        return ssoRedirectUrl;
    }

    public void setSsoRedirectUrl(String ssoRedirectUrl) {
        this.ssoRedirectUrl = ssoRedirectUrl;
    }

    public String[] getSsoWhite() {
        return ssoWhite;
    }

    public void setSsoWhite(String[] ssoWhite) {
        this.ssoWhite = ssoWhite;
    }

    public boolean isEnableMdc() {
        return enableMdc;
    }

    public void setEnableMdc(boolean enableMdc) {
        this.enableMdc = enableMdc;
    }

    public String getMdcKeyword() {
        return mdcKeyword;
    }

    public void setMdcKeyword(String mdcKeyword) {
        this.mdcKeyword = mdcKeyword;
    }
}
